#include "NScriptLoader.h"
#include "NScriptTraverser.h"
#include "ScopeManager.h"
#include "neiderra/utils/Logger.h"
#include "neiderra/utils/Lexer.h"
#include "neiderra/utils/CommonFunctions.h"
#include "neiderra/core/iException.h"
#include "iSymtable.h"
#include "nsNodes.h"
#include "neiderra/utils/vm/VirtualMachine.h"

using namespace neiderra::utils;
using namespace neiderra::core;
using namespace neiderra::utils::lexer;
using namespace neiderra::utils::script;
using neiderra::utils::intToStr;

#define log(s) core->getLogger()->log(s)
#define trace(s) core->getLogger()->traceBlock(s)
#define leave core->getLogger()->leaveBlock()

NScriptLoader::NScriptLoader(Core* core)
:	Base(core),
	lexer_ ( new Lexer(core))
{
	input_=0;
	_symtable = new Symtable(core);
	scoper_ = new ScopeManager(core);
}

NScriptLoader::~NScriptLoader()
{
	delete _symtable;
	delete scoper_;
}

Symtable* NScriptLoader::getSymtable()
{
	return _symtable;
}

void NScriptLoader::prepareSymtable()
{
	// clear sym
	// fixme: clear symtable
	// getSymtable()->clear();

 	// init sym
 	tInt = new nsClass;
	tInt->setSize(sizeof(int));
	tInt->setEmbedded(true);

	tChar = new nsClass;
	tChar->setSize(sizeof(char));
	tChar->setEmbedded(true);

	tFloat = new nsClass;
	tFloat->setSize(sizeof(float));
	tFloat->setEmbedded(true);
	
	tDouble = new nsClass;
	tDouble->setSize(sizeof(double));
	tDouble->setEmbedded(true);

	tBool = new nsClass;
	tBool->setSize(sizeof(bool));
	tBool->setEmbedded(true);

	tVoid = new nsClass;
	tVoid->setEmbedded(true);

 	_symtable->insert("int", tInt);
 	_symtable->insert("char", tChar);
 	_symtable->insert("float", tFloat);
 	_symtable->insert("double", tDouble);
 	_symtable->insert("bool", tBool);
 	_symtable->insert("void", tVoid);
}

nsNode* NScriptLoader::loadFromStream(iInputStream* input)
{
	trace("NScriptLoader::loadFromStream");
	nsFunc* n=0;
	nsNode* stmt;

	input_ = input;

	if(!input_) {
		throw iException("bad input stream");
	}
	input_->seek(0, stBegin);

	lexer_->setInputStream(input);
	lexer_->skipComments(csCPP);

	match(tokBOFTOK);	// match file start

	prepareSymtable();

	setCurrentClass( 0 );
	setCurrentFunction( 0 );

	n = new nsFunc;
	
//	n->setName("function");
//  n->addParam("name", "main");
//  n->addParam("type", "void");

	n->body = new nsBlock;
	n->body->parent = 0;
	n->body->stmts = new nsNodes;
	n->params = 0;
	n->setName("main");
	n->setType(tVoid);
 	
	
 	try{
		while(!next(tokEOFTOK))
		{
			stmt = read_stmt(n->getBody());
			if(stmt)
				stmt->setParent( 0 );
			n->getBody()->getStmtList()->add(stmt);
		}
	}
	catch(EBadToken e)
	{
		// fixme: continue parse
		ex_fatal(e.getReport());
	}
	
	leave;
	return n->body;
}	

// from '{' to '}'
nsNode* NScriptLoader::read_block( nsNode* parent )
{	
	trace("read_block");
	match(tokLFBRACKET);

	match(tokRFBRACKET);
	leave;
	return 0;
}

nsFunc* NScriptLoader::read_func(nsNode* parent)
{
	try
	{
		trace("read_func");
		nsFunc* funcnode=0;
		nsNodes* params=0;
	
		string type_name = getNearestNameOf(_currentToken.value.sval);
		
		//nsNode* typenode = _symtable->getNode(type_name);
		nsClass* typenode = getTypeNode(type_name);
		
		if(!typenode) 
		{
			throw EBadFunc(string(_currentToken.value.sval)+" used as a type, but it is not a type");
		}
		match(tokID);
	
		if(!next(tokID)) 
		{
			throw EBadFunc("function name expected");
		}

		string funcname = scoper_->getScope()+_currentToken.value.sval;
	
		scoper_->openScope(_currentToken.value.sval);
	
		match(tokID);
	
		match(tokLBRACKET);
	
		funcnode = new nsFunc;
		funcnode->setName(funcname);
	
		setCurrentFunction( funcnode );

		params = read_formal_args( funcnode );
	
		match(tokRBRACKET);
	
		funcnode->setType( typenode );
		funcnode->stype = type_name;
		funcnode->params = params;
		funcnode->body=0;
		funcnode->parent = parent;
	
		_symtable->insert(funcnode->getName(), funcnode->getType());

		if(next(tokLFBRACKET))
		{
			read_def_func( funcnode );
		}
		
		setCurrentFunction( 0 );

		scoper_->closeScope();
	
		leave;
		return funcnode;
	}
	catch(iException e)
	{
		throw EBadFunc(e.getReport());
	}
} 

nsVar* NScriptLoader::read_dec_var( nsNode* parent )
{
	trace("read_dec_var");
	nsVarDec* varnode=0;

	match(tokID);	// var                          
	string tname = _currentToken.value.sval;
	if(!isType(tname))
	{
		throw EBadVar(tname + " is not a typename");
	}
	
	// get real type name
	if(!isEmbeddedType(tname))
		tname = getNearestNameOf(tname);

	match(tokID);	// type

	string varname = scoper_->getScope()+_currentToken.value.sval;

	match(tokID);
	
	nsClass* vartype = getTypeNode(tname);
	if(!vartype)
		throw EBadVar("variable " + varname + " undeclared");

	varnode = new nsVarDec;
	varnode->setName(varname);
	//varnode->type = _symtable->getNode(tname);
	varnode->setType(vartype);
	varnode->stype = tname;
	varnode->parent = parent;

	if(getCurrentClass() || !vartype->isEmbedded())
		varnode->setSize(VM_POINTER_SIZE);
	else
		varnode->setSize(vartype->getSize());

	_symtable->insert(varnode);

	// if we're in the function or method, then declaration is local
	if(getCurrentFunction())	
	{
		varnode->setIsLocal(true);
		getCurrentFunction()->addLocal(varnode);
		varnode->setIsField(false);
	}
	else if(getCurrentClass()) // if we're in the class, but not in method
	{
		varnode->setIsField(true);
	}

	log(string("var name: ")+varname);
	leave;
	return varnode;
}

nsNodes* NScriptLoader::read_formal_args( nsFunc* parent )
{
	trace("read_formal_args");
	nsNodes* params = new nsNodes;
	nsArg* tmparg=0;
	string tmpname;
	string argname;
	nsClass* tmptype=0;
	string type_name;

	int pc=0;
	
	while(!next(tokRBRACKET))
	{
		if(!nextIsType()) {
			throw iException("type expected");
		}
		
		type_name = _currentToken.value.sval;
		tmptype = //_symtable->getNode(_currentToken.value.sval);
			getTypeNode(type_name);

		match(tokID);

		if(!next(tokID)) {
			throw iException("arg name expected");
		}
		
		argname = scoper_->getScope() + _currentToken.value.sval;

		char buf[2];
		tmpname = scoper_->getScope();
		neiderra::utils::itoa(pc, buf, 10);
		tmpname.append(buf);

		match(tokID);

		if(!next(tokRBRACKET)) match(tokCOMMA);

		tmparg = new nsArg;
		tmparg->setName(argname);
		tmparg->setType(tmptype);
		tmparg->parent = parent;
		tmparg->stype = type_name;

		params->add(tmparg);

		_symtable->insert(tmpname, tmptype);		// $function$0
		_symtable->insert(argname, tmparg);			// $function$a

		getCurrentFunction()->addLocal(tmparg);

		//log(string("new formal arg: ")+tmparg->name);
		log(string("new arg: ")+argname);

		pc++;

//                log("ctok: ");
//                log((int)_currentToken.id);
	}
	leave;
	return params;
}

nsNodes* NScriptLoader::read_actual_args()
{
	nsNodes* args = new nsNodes();
	nsAArg* aarg=0;
	// fixme: implement read_actual_args
	while(!next(tokRBRACKET))
	{
		if(!next(tokID)){
			throw iException("arg expected");
		}

		aarg = new nsAArg;
		aarg->setLeft( read_expr (aarg) );

		args->add(aarg);

		if(!next(tokRBRACKET)) 
			match(tokCOMMA);
	}

	return args;
}

nsFunc* NScriptLoader::read_def_func( nsFunc* parent )
{
	trace("read_def_func");

	nsNode* n;
	
	match(tokLFBRACKET);
	parent->setBody( new nsBlock );
	parent->getBody()->setStmtList( new nsNodes );
	parent->getBody()->getStmtList()->setParent( parent->getBody());

	parent->getBody()->setParent( parent );

	while(!next(tokRFBRACKET)){
		n = read_stmt(parent->getBody());
		if(n) n->setParent(parent);
		parent->getBody()->getStmtList()->add(n);
	}

	match(tokRFBRACKET);
	
	leave;
	return parent;
}

nsNode* NScriptLoader::read_stmt( nsBlock* parent )
{
	try
	{
		trace("read_stmt");
		string val;
	
		nsExpr* exprnode=0;
		nsExpr* derefexpr=0;
		nsVar* varnode=0;
		nsNumber* numnode=0;
		nsFunc*	funcnode=0;
		nsRetStmt* retstmt=0;
		nsClass* classnode=0;
		nsRef* refnode=0;
	
		nsNode* tmp=0;
	
		switch(_currentToken.id)
		{
		case tokID: 
			val = _currentToken.value.sval;
			if( val == "class" ) {
				//log("class declaration found");
				classnode = read_class( parent );
				match(tokEOS);
				leave;
				return classnode;
			}		
			else if ( val == "return" )
			{
				log("return stmt");
				match(tokID);
				retstmt = new nsRetStmt;
				retstmt->operation = OT_RETURN;
				retstmt->right = read_expr( retstmt );
				retstmt->left = 0;
				match(tokEOS);
				leave;
				return retstmt;
			}
			else if ( val == "var" ) {
				//log("variable declaration found");
				varnode = read_dec_var(parent);
				match(tokEOS);
				leave;
				return varnode;
			}
			else if ( isType(val) ) {
				//log("function stmt found");                                                 
				funcnode = read_func( parent );
				funcnode->parent = parent;
				
				log(string("scope is: ") + scoper_->getScope());
				leave;
				return funcnode;
			}
	
			else {
				//log("stmt found");	// function call, or 'id = expr'
				match(tokID);
	
				if(next(tokLBRACKET))
				{	// function call
					// todo: function call
				}
				else{
					exprnode = new nsExpr;
	
					string varname = val;
					varname = getNearestNameOf(varname);
					tmp = _symtable->getNode(varname);
					if(!tmp) {
						throw iException(varname+" undeclared");
					}

					if(next(tokDOT))
					{
						refnode = new nsRef;
						refnode->setName(varname);
						refnode->setType(tmp->getType());
						exprnode->setLeft(refnode);
						refnode->setRef(read_deref( refnode ));
					}
					else
					{
						varnode = new nsVar;
						varnode->setName(varname);
						varnode->setType(tmp->getType());
						exprnode->setLeft(varnode) ;

						// test for variable locality
						if(getCurrentFunction()->getLocals().getByName(varnode->getName()))
							varnode->setIsLocal(true);
					}
					
					exprnode->setOperationType(OT_ASSIGN);
					exprnode->setParent(parent);
					
					match(tokASSIGN);
	
					if(!tmp) 
					{
						throw iException(varname + " undeclared");
					}
	
					exprnode->setRight(read_expr(exprnode->getRight()));
					match(tokEOS);
					leave;
					return exprnode;
				}
				match(tokEOS);
			}
			break;
		default: 
			throw EBadToken("Id or type expected");
		}
		leave;
		return parent;
	}
	catch(EBadClass e)
	{
		// fixme: try to continue with next stmt
		ex_fatal(e.getReport());
	}
	catch(EBadFunc e)
	{
		ex_fatal(e.getReport());
		// fixme: try to continue with next stmt
	}
}

nsClass* NScriptLoader::read_class( nsNode* parent )
{
	try{
		trace("class");
		
		nsFunc* funcnode=0;
		nsVar* fieldnode=0;
		nsClass* classnode = new nsClass();
		classnode->parent = parent;
		nsRef* ref=0;
		
		match(tokID);
		
		// name
		if(!next(tokID))
		{
			throw EBadToken("class name expected");
		}

		string name = _currentToken.value.sval;
		classnode->setName(scoper_->getScope() + name);
		match(tokID);
		
		// insert new type into symtable
		_symtable->insert(classnode->getName(), classnode);
		
		scoper_->openScope(name);	

		setCurrentClass( classnode );
		
		match(tokLFBRACKET);
		
		while(1)
		{
			if(next(tokID))
			{
				string val = _currentToken.value.sval;
				if(isType(val))		// method
				{
					funcnode = read_func( parent );
					funcnode->parent = classnode;

					// todo: addMethod method
					classnode->methods.add(funcnode);
				}
				else if(val == "var")	// field
				{
					// fixme: cleanup
					fieldnode = read_dec_var( fieldnode );
					fieldnode->setParent(classnode);

					// todo: addField method
					classnode->fields.add(fieldnode);
					match(tokEOS);
				}
			}
			else 
				break;
		}
		
		setCurrentClass(0);

		match(tokRFBRACKET);
		
		scoper_->closeScope();
		
		log(classnode->getName());
		
		leave;
		return classnode;
	}
	catch(EBadVar e)
	{
		throw EBadClass(e.getReport());
	}
	catch(EBadFunc e)
	{
		throw EBadClass(e.getReport());
	}
	catch(iException e)
	{
		throw e;
	}
}

nsOperand* NScriptLoader::read_expr( nsNode* parent )
{
	trace("expr");
	nsOperand* lval=0;
	nsExpr* rval=0;
	lval = read_term( parent );

	if(!next(tokPLUS)&&!next(tokMINUS)) 
	{
		leave;
		return lval;
	}
	
	rval = new nsExpr;
	rval->left = lval;
	while(1){
		if(next(tokPLUS)||next(tokMINUS))
		{
			rval->operation = next(tokPLUS)?OT_PLUS:OT_MINUS;
			pass();
			rval->right = read_expr( rval );
			leave;
			return rval;
		}
		else{
			leave;
			return rval;
		}
	}
}

nsOperand* NScriptLoader::read_term( nsNode* parent )
{
	trace("term");
	nsOperand* lval=0;
	nsExpr* rval=0;

	lval = read_factor ( parent );

	if(!next(tokSLASH)&&(!next(tokSTAR))){
		leave;
		return lval;
	}
	
	rval = new nsExpr;
	rval->left = lval;

	while(1){

		if (next(tokSLASH)||next(tokSTAR))
		{
			rval->operation = next(tokSLASH)?OT_DIV:OT_MULT;
			
			pass();
			rval->right = read_term( rval );
			leave;
			return rval;
		}
		else{
			leave;
			return rval;
		}
	}
}

nsOperand* NScriptLoader::read_deref(nsOperand* parent)
{
	trace("deref");
	nsRef* deref=0;
	nsMethodCall* derefm=0;
	
	match(tokDOT);
	if(!next(tokID))
	{
		// fixme: give more info
		throw EBadToken("id expected");
	}
		
	string name = _currentToken.value.sval;
	
	// check if parent is class or structure
	nsClass* parentType = dynamic_cast<nsClass*>(parent->getType());
	if(parentType == 0)
	{
		throw EBadVar(parent->getName() + " is not a class or structure");
	}
	
	string scopedName = parentType->getName() + "$" + name;
	
	match(tokID);
	
	// get info about field or method
	if(next(tokLBRACKET))		// method
	{
		nsNode* method = parentType->getMethods().getByName(scopedName);

		if(!method)
		{
			throw EBadClass(scopedName + " is not a method of " + parentType->getName());
		}

		derefm = new nsMethodCall;
		derefm->setName(scopedName);
		derefm->setType(method->getType());
		log("name: " + scopedName);

		pass();
		
		// fixme: read actual args
		derefm->setParams(read_actual_args());

        match(tokRBRACKET);

		leave;
		return derefm;
	}
	else	// field
	{
		nsNode* field = parentType->getFields().getByName(scopedName);

		if(!field)
		{
			throw EBadClass(scopedName + " is not a field of " + parentType->getName());
		}

		deref = new nsRef;
		deref->setName(scopedName);
		deref->setType(field->getType());
		log("name: " + scopedName);

		leave;
		return deref;
	}
	
	// todo 1: continue parsing
	
}

nsOperand* NScriptLoader::read_factor( nsNode* parent )
{
	trace("factor");

	nsVar* varnode=0;
	nsFCall* fcallnode=0;
	nsNumber* numnode=0;
	nsExpr* exprnode=0;
	nsNode* tmp=0;
	nsNode* symnode=0;
	string tmpname;
	nsNodes* args=0;
	nsOperand* tmparg=0;
	nsAArg* aarg=0;
	nsNode* argtype=0;
	nsRef* refnode=0;
	nsSelfRef* selfRefnode=0;

	// todo unary '-'
	switch(_currentToken.id)
	{
		case tokID:
			log("var or function call...");
			if(isType(_currentToken.value.sval)) 
				log("typename can't be name of the operand in expression");
//			varnode = new nsVar;
//			tmpname = scoper_->getScope()+_currentToken.value.sval;
			tmpname = _currentToken.value.sval;
			tmpname = getNearestNameOf(tmpname);
			symnode = _symtable->getNode(tmpname);
			if(!symnode) {
				throw iException("var undeclared");
			}
			match(tokID);

			if(next(tokLBRACKET)){	// function call
				log("function call");
				match(tokLBRACKET);

				args = new nsNodes;
				int c=0;

				// fixme: update
				while(!next(tokRBRACKET))
				{
					if(!next(tokID)){
						throw iException("arg expected");
					}

					tmparg = read_expr (tmparg);
					aarg = new nsAArg;
					aarg->left = tmparg;

					//args->add(tmparg);
					args->add(aarg);

					if(!next(tokRBRACKET)) match(tokCOMMA);
					c++;
				}

				match(tokRBRACKET);

				// if this is call from the method
				if(getCurrentClass())
				{
					selfRefnode = new nsSelfRef;
					nsMethodCall* refmethod = new nsMethodCall;
					refmethod->setName(tmpname);
					refmethod->setType(symnode->getType() );
					refmethod->setParams(args);
					selfRefnode->setRef(refmethod);
					leave;
					return selfRefnode;
				}
				else{
					fcallnode = new nsFCall;
					fcallnode->setName( tmpname );
					fcallnode->setType( symnode->getType() );
					fcallnode->setParams( args );
					leave;
					return fcallnode;
				}
			}
			else if(next(tokDOT))	// link dereferencing: 'a.b' or 'a.f()' or 'a.x.y...n.f()' & etc.
			{
				// lvalue
				refnode = new nsRef;
				refnode->setName( tmpname );
				refnode->setType( symnode->getType() );
				
				refnode->setRef( read_deref( refnode ) );

				// if we're inside the method
				if(getCurrentClass())
				{
					selfRefnode = new nsSelfRef;
					selfRefnode->setRef(refnode);
					leave;
					return selfRefnode;
				}
				
				leave;
				return refnode;				
			}
			else{
				varnode = new nsVar;
				varnode->setType( symnode->getType() );
				varnode->setName( tmpname );

				// test variable for locality
				if(getCurrentFunction()->getLocals().getByName( tmpname ))
				{
					varnode->setIsLocal(true);
					leave;
					return varnode;
				}
				
				if(getCurrentClass()) 
				{
					selfRefnode = new nsSelfRef;
					selfRefnode->setRef(varnode);
					leave;
					return selfRefnode;

				}
                
				leave;
				return varnode;
			}

		case tokNUM_INTEGER: case tokNUM_FLOAT:
			log("number");
			numnode = new nsNumber;
			if(next(tokNUM_INTEGER)){
				numnode->value.ival = _currentToken.value.num_int;
				numnode->setType( tInt );
			}
			else if(next(tokNUM_FLOAT)){
				numnode->value.fval = _currentToken.value.num_float;
				numnode->setType( tFloat );
			}
			pass();
			leave;
			return numnode;			
//			break;
		case tokLBRACKET:
			log("expression");
			match(tokLBRACKET);
			exprnode->setLeft( read_expr( exprnode ) );
			exprnode->setRight( 0 );
			match(tokRBRACKET);
			leave;
			return exprnode;
//			break;
		default:
			log("factor::error real token id = ");
			log((int)_currentToken.id);
			throw EBadToken("");
	}
}

void NScriptLoader::match(tokens tok)
{                        
	if(_currentToken.id!=tok) {
		log("formal: ");
		log((int)tok);
		log("real: ");
		log((int)_currentToken.id);

		// fixme: give more information
		throw EBadToken("wrong token found");
	}
	_currentToken = lexer_->nextToken();
}

bool NScriptLoader::isEmbeddedType(const string& s)
{
	if(s == "int" || s=="char" || s=="float" || s=="double" || s=="bool" ) { return true;}
	else return false;
}

bool NScriptLoader::isType(const std::string& s)
{
	if(isEmbeddedType(s))
		return true;

	string realName = getNearestNameOf(s);
	nsNode* symnode = _symtable->getNode(realName);
	if(symnode)
	{
		if(symnode->getType() == 0)
			return true;
		else 
			return false;
	}
	else
		return false;
        

	
/*	else 
	{
		// class A { 			// $A is the class name
		//    A x;              // $A$A is the scope name, $A is the real name
		string realName = getNearestNameOf(s);
		nsNode* typenode = _symtable->getNode(realName);
		if(typenode)
		{
			// check if the type is class
			if(dynamic_cast<nsClass*>(typenode)) return true;
			//else throw iException("strange: found type " + s + ", but it's node is not a class");
			
			return false;
		}
		else return false;
	}*/
}

bool NScriptLoader::nextIsType()
{
	if(next(tokID)) { 
		//return _symtable->getNode(_currentToken.value.sval)==0?false:true; 
		return isType(_currentToken.value.sval);
	}
	else return false;
}

bool NScriptLoader::next(tokens tok)
{
	return (_currentToken.id==tok)?true:false;
}

nsNode* NScriptLoader::searchForDec(const string& name)
{
/*	string cs="";
	while(cs!="@"){
		cs = scoper_->getScope();

	}*/
	return 0;
}

void NScriptLoader::pass()
{
	_currentToken = lexer_->nextToken();
}

string NScriptLoader::getNearestNameOf(const string& idname)
{
	string name = idname;
	nsNode* t=0;
	try 
	{
		t = _symtable->getNode(scoper_->getScope()+name);
		if(t) name = scoper_->getScope()+name;
		while(1)
		{
			t=_symtable->getNode(scoper_->prev()+name);
			if (t) 
			{
				name = scoper_->prev_current()+name;
				scoper_->reset();
				break;
			}
		}
	}            
	catch(ScopeManager::top)
	{
		scoper_->reset();
	}
	return name;
}

nsClass* NScriptLoader::getTypeNode(const string& type)
{
	nsNode* symnode = _symtable->getNode(type);
	if(!symnode)
		return 0;

	nsClass* typenode = symnode->getType();
	if(!typenode)	// it's a class
	{
		return (nsClass*)symnode;
	}
	else{			// it's variable
		return typenode;
	}

	/*nsNode* t=0;
	try
	{
		t = _symtable->getNode(TypeName);
		if(t) {
			return t;}
		else
		{
			while(1)
			{
				string x = scoper_->prev()+TypeName;
				t=_symtable->getNode(x);
				if (t) 
				{
					scoper_->reset();
					return t;
				}
			}
		}
	}
	catch(ScopeManager::top)
	{
		scoper_->reset();
		return 0;
	}*/
}

unsigned NScriptLoader::typesize(nsNode* n)
{
	// fixme: get rid of this shit...
	if(n==tInt) return 4;
	else if(n==tChar) return 1;
	else if(n==tFloat) return 4;
	else if(n==tDouble) return 4;
	else if(n==tBool) return 1;
	else if(n==tVoid) return 0;
	else return n->size();		
}

/*
strange time had come. I'm going to use Boost.Python
*/

